/**
 * @author Vitor Franco do Carmo (vitor.carmo@cpmbraxis.com)
 * @since 30/07/2013
 *
 */
package br.com.vfcfw.business.service;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;

import br.com.vfcfw.commons.domain.entity.BaseEntity;
import br.com.vfcfw.commons.domain.qualifier.Obrigatorio;
import br.com.vfcfw.commons.exception.ApplicationException;
import br.com.vfcfw.commons.exception.BusinessException;
import br.com.vfcfw.commons.util.ConstantesArquitetura;
import br.com.vfcfw.commons.util.GenericsUtil;
import br.com.vfcfw.commons.util.MensagemUtil;
import br.com.vfcfw.commons.util.ReflexaoUtil;
import br.com.vfcfw.commons.util.StringUtil;
import br.com.vfcfw.persistence.dao.BaseJPAHibernateDAO;
import br.com.vfcfw.persistence.exception.PersistenceException;

/**
 * Classe base para todas as classes de negócio.
 * 
 * @author Vitor Franco do Carmo (vitor.carmo@cpmbraxis.com)
 * @since 30/07/2013
 * 
 */
public abstract class BaseService<E extends BaseEntity> implements IBaseService<E> {

    /** */
    private static final long serialVersionUID = -1902219165637465077L;

    @SuppressWarnings("unchecked")
    protected Class<E> entityClass = GenericsUtil.getSuperClassGenericType(getClass());

    private BaseJPAHibernateDAO<E> entityDAO;

    private List<String> erros = new ArrayList<String>();

    /**
     * Construtor da classe.
     */
    public BaseService() {

        super();
    }

    /**
     * Método para setar a classe mapeada com JPA, principal do service, no {@link BaseJPAHibernateDAO}.
     */
    @PostConstruct
    public void init() {

        entityDAO.setEntityClass(entityClass);
    }

    /**
     * Método a ser implementado e anotado com <b>@PostConstruct</b> para inicializar o {@link BaseJPAHibernateDAO}.
     */
    @PostConstruct
    public abstract void inicializar();

    /**
     * Remove uma entidade do gerenciamento da sessão (JPA/Hibernate).
     * 
     * @param entity Entidade mapeada com JPA.
     */
    public void removerGerenciamentoSessao(E entity) {

        getEntityDAO().removerGerenciamentoSessao(entity);
    }

    /**
     * Retorna a {@link Connection} conexão com o banco de dados. <p>Deve ser usado somente como fonte de dados dos
     * relatórios</p>.
     * 
     * @return {@link Connection}.
     */
    public Connection obterConexao() {

        return this.getEntityDAO().getConexao();
    }

    /**
     * Retorna o valor do atributo entityDAO.
     * 
     * @return Valor do atributo entityDAO.
     */
    public BaseJPAHibernateDAO<E> getEntityDAO() {

        return entityDAO;
    }

    /**
     * Especifica o valor do atributo entityDAO.
     * 
     * @param entityDAO BaseJPAHibernateDAO<E> do entityDAO a ser setado.
     */
    public void setEntityDAO(BaseJPAHibernateDAO<E> entityDAO) {

        this.entityDAO = entityDAO;
    }

    /**
     * Retorna o valor do atributo entityClass.
     * 
     * @return Valor do atributo entityClass.
     */
    public Class<E> getEntityClass() {

        return entityClass;
    }

    /**
     * Valida o preenchimento do identificador da entidade persistente.
     * 
     * @param entity Entidade mapeada com JPA.
     * @throws BusinessException
     */
    protected void validarPk(E entity) {

        if (!possuiValor(entity)) {

            adicionarErro(ConstantesArquitetura.MSG_ERRO_OBJETO_NULO, entityClass.getSimpleName());

        } else {

            if (!possuiValor(entity.getChavePrimaria())) {

                adicionarErro(ConstantesArquitetura.MSG_ERRO_CAMPO_OBRIGATORIO_NAO_PREENCHIDO, "ID");
            }
        }
    }

    /**
     * Valida o preenchimento dos campos obrigatórios.
     * 
     * @param entity Entidade mapeada com JPA.
     * @throws BusinessException
     */
    protected void validarCampos(E entity) throws BusinessException {

        if (!possuiValor(entity)) {

            adicionarErro(ConstantesArquitetura.MSG_ERRO_OBJETO_NULO, entityClass.getName());

        } else {

            try {

                Map<String, Object> camposObrigatorios = ReflexaoUtil.obterCamposAnotados(Obrigatorio.class,
                    entityClass, entity);

                for (Entry<String, Object> entry : camposObrigatorios.entrySet()) {

                    if (!possuiValor(entry.getValue())) {

                        adicionarErro(ConstantesArquitetura.MSG_ERRO_CAMPO_OBRIGATORIO_NAO_PREENCHIDO,
                            obterNomeCampo(entry.getKey()));
                    }
                }
            } catch (Exception e) {
                throw new BusinessException(getMensagem(ConstantesArquitetura.MSG_ERRO_INTERNO_SISTEMA), e);
            }
        }
    }

    /**
     * Adiciona uma mensagem de erro na lista de erros.
     * 
     * @param mensagem Mensagem de erro.
     */
    protected void adicionarErro(String mensagem) {

        if (!getErros().contains(mensagem)) {

            getErros().add(mensagem);
        }
    }

    /**
     * Adiciona uma mensagem de erro na lista de erros.
     * 
     * @param chave Chave do texto no arquivo de recurso.
     * @param argumentos Argumentos que substituirão os curingas no texto.
     */
    protected void adicionarErro(String chave, Object... argumentos) {

        String msg = getMensagem(chave, argumentos);

        if (!getErros().contains(msg)) {

            getErros().add(msg);
        }
    }

    /**
     * Processa as validações realizadas. Caso exista um ou mais erros será lançado uma {@link BusinessException} com a
     * lista de erros encontrados.
     * 
     * @throws BusinessException
     */
    protected void processarValidacoes() throws BusinessException {

        if (!getErros().isEmpty()) {

            List<String> listaErros = new ArrayList<String>(getErros());
            getErros().clear();

            throw new BusinessException(listaErros);
        }
    }

    /**
     * Realiza o tratamento das exceptions
     * 
     * @param e {@link Exception}.
     */
    protected void tratarException(Exception e) {

        Exception exception = null;

        if (e != null && e.getCause() != null) {

            exception = (Exception) e.getCause();
        } else {
            exception = e;
        }

        if (exception instanceof NullPointerException) {

            throw new ApplicationException(exception.getMessage(), exception);

        } else if (exception instanceof PersistenceException) {

            throw new BusinessException(exception.getMessage(), exception);

        } else {

            throw new BusinessException(exception);
        }

    }

    /**
     * Retorna o nome da entidade mapeada com JPA definido no arquivo de recurso. Caso não esteja definido, o SimpleName
     * será retornado.
     * 
     * @param entityClass Classe da entidade mapeada com JPA (<tt>o simple name será a chave no arquivo de
     *        recurso</tt>).
     * @return Nome da entidade mapeada com JPA.
     */
    protected String obterNomeEntity(Class<E> entityClass) {

        String nomeEntity = null;

        nomeEntity = getMensagem(String.format("label.%s", entityClass.getSimpleName()));

        if (!StringUtil.possuiValor(nomeEntity)) {

            nomeEntity = entityClass.getSimpleName();
        }

        return nomeEntity;
    }

    /**
     * Retorna o nome do campo correspondente ao nome do atributo informado. <p>Procura no arquivo de recurso uma chave
     * que possua a seguinte estrutura: <br /> label.[NOME_CLASSE_PERSISTENTE].[NOME_ATRIBUTO].</p><p>Ex.: <br
     * />label.TipoConflito.nome = Tipo de Conflito</p>
     * 
     * @param nomeAtributo Nome do atributo.
     * @return Nome do campo.
     */
    protected String obterNomeCampo(String nomeAtributo) {

        String chaveNomeCampo = String.format("label.%s.%s", entityClass.getSimpleName(), nomeAtributo);
        String nomeCampo = getMensagem(chaveNomeCampo);

        if (!possuiValor(nomeCampo)) {

            nomeCampo = nomeAtributo;
        }
        return nomeCampo;
    }

    /**
     * Retorna o texto, no arquivo de recurso, correspondente a chave passada para localidade padrão.
     * 
     * @param chave Chave do texto no arquivo de recursos.
     * @param argumentos Argumentos que substituirão os curingas no texto.
     * @return Texto.
     */
    protected String getMensagem(String chave, Object... argumentos) {

        return MensagemUtil.obterMensagem(chave, argumentos);
    }

    /**
     * Retorna o texto, no arquivo de recurso, correspondente a chave passada para localidade informada.
     * 
     * @param locale {@link Locale} Localidade.
     * @param chave Chave do texto no arquivo de recursos.
     * @param argumentos Argumentos que substituirão os curingas no texto.
     * @return Texto.
     */
    protected String getMensagem(Locale locale, String chave, Object... argumentos) {

        return MensagemUtil.obterMensagem(locale, chave, argumentos);
    }

    /**
     * Verifica se o valor é nulo ou vazio.
     * 
     * @param valor Valor.
     * @return <tt>TRUE</tt> se não for <b>nulo</b> ou <b>vazio</b>, <tt>FALSE</tt> caso contrário.
     */
    protected boolean possuiValor(Object valor) {

        return StringUtil.possuiValor(valor);
    }

    /**
     * Retorna o valor do atributo erros.
     * 
     * @return Valor do atributo erros.
     */
    private List<String> getErros() {

        if (this.erros == null) {

            this.erros = new ArrayList<String>();
        }

        return erros;
    }

}
